package com.examples.util;

import com.cloud.apigateway.sdk.utils.Client;
import com.cloud.apigateway.sdk.utils.Request;
import com.examples.model.RestfulResponse;
import com.fasterxml.jackson.core.JsonProcessingException;

import org.apache.http.Header;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.springframework.http.HttpHeaders;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.SSLContext;

public enum HttpClientUtils {
    INSTANCE;

    private static final Logger log = org.slf4j.LoggerFactory.getLogger(HttpClientUtils.class);

    private static HttpClientBuilder httpClientBuilder;

    private static PoolingHttpClientConnectionManager connectionManager;

    public static RestfulResponse get(String uri, Map<String, String> queryParamMap, String ak, String sk)
        throws CustomException {
        Request request = new Request();
        HttpRequestBase signedRequest;
        try {
            generateHeader(request, null);

            // Set the AK/SK to sign and authenticate the request.
            request.setKey(ak);
            request.setSecret(sk);

            // Specify a request method, such as GET, PUT, POST, DELETE, HEAD, and PATCH.
            request.setMethod("GET");

            // Set a request URL in the format of https://{Endpoint}/{URI}.
            request.setUrl(uri);

            // Add header parameters, for example, x-domain-id for invoking a global service and x-project-id for
            // invoking a project-level service.
            request.addHeader("Content-Type", ContentType.APPLICATION_JSON.toString());

            for (Map.Entry<String, String> entry : queryParamMap.entrySet()) {
                request.addQueryStringParam(entry.getKey(), entry.getValue());
            }
            // Add a body if you have specified the PUT or POST method. Special characters, such as the double quotation
            // mark ("), contained in the body must be escaped.
            signedRequest = Client.sign(request);

            return sendRequestReturnRaw(signedRequest);
        } catch (JsonProcessingException e) {
            log.error("failed to send get request catch JsonProcessingException", e);
            throw new CustomException(
                ResultCodeEnum.OTHER_INNER_ERROR.getResultCode(),
                "failed to send get request catch JsonProcessingException",
                e);
        } catch (Exception e) {
            log.error("failed to send get request catch sdk inner exception", e);
            throw new CustomException(
                ResultCodeEnum.OTHER_INNER_ERROR.getResultCode(),
                "failed to send get request catch sdk inner exception",
                e);
        }
    }

    private static RestfulResponse sendRequestReturnRaw(HttpRequestBase httpRequest)
        throws CustomException {
        RestfulResponse restfulResponse = new RestfulResponse();

        try {
            CloseableHttpClient httpClient = INSTANCE.getHttpClient();
            CloseableHttpResponse response = httpClient.execute(httpRequest);
            int statusCode = response.getStatusLine().getStatusCode();
            String resultString;
            if (response.getEntity() == null) {
                resultString = null;
            } else {
                resultString = EntityUtils.toString(response.getEntity(), Charset.defaultCharset());
            }
            response.close();

            Map<String, String> responseHeaderMap = new HashMap<>();
            for (Header header : response.getAllHeaders()) {
                responseHeaderMap.put(header.getName(), header.getValue());
            }
            restfulResponse.setStatus(statusCode);
            restfulResponse.setResponseJson(resultString);
            restfulResponse.setRespHeaderMap(responseHeaderMap);
        } catch (IOException e) {
            restfulResponse.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR);
            restfulResponse.setResponseJson("Failed to execute http request");
            log.error("failed to send request", e);
        } finally {
            httpRequest.releaseConnection();
        }
        return restfulResponse;
    }

    private static Registry<ConnectionSocketFactory> getDefaultRegistry(
        SSLConnectionSocketFactory sslConnectionSocketFactory) {
        return RegistryBuilder.<ConnectionSocketFactory>create()
            .register("http", PlainConnectionSocketFactory.getSocketFactory())
            .register("https", sslConnectionSocketFactory)
            .build();
    }

    private static void generateHeader(Request httpRequest, Map<String, String> headerMap) {
        if (!CollectionUtils.isEmpty(headerMap)) {
            headerMap.forEach(httpRequest::addHeader);
        } else {
            httpRequest.addHeader("Content-Type", ContentType.APPLICATION_JSON.toString());
        }

        setAcceptEncoding(httpRequest);
    }

    private static void setAcceptEncoding(Request httpRequest) {
        if (httpRequest.getHeaders().get(HttpHeaders.ACCEPT_ENCODING) == null) {
            httpRequest.addHeader(HttpHeaders.ACCEPT_ENCODING, "*");
        }
    }

    private CloseableHttpClient getHttpClient() throws CustomException {
        // 生产环境请业务根据要求自己选择,此处仅仅是样例
        SSLContext sslContext;
        try {
            sslContext = SSLContexts.custom().loadTrustMaterial(new TrustAllStrategy()).build();
        } catch (KeyStoreException | NoSuchAlgorithmException | KeyManagementException e) {
            log.error("create ssl content error.", e);
            throw new CustomException(ResultCodeEnum.OTHER_INNER_ERROR.getResultCode(),
                "create ssl content error!", e);
        }
        String[] supportedCipherSuites = {
            "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
        };
        SSLConnectionSocketFactory sslConnectionSocketFactory =
            new SSLConnectionSocketFactory(
                sslContext,
                new String[] {"TLSv1.2"},
                supportedCipherSuites,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());

        HttpClientUtils.connectionManager =
            new PoolingHttpClientConnectionManager(getDefaultRegistry(sslConnectionSocketFactory));
        connectionManager.setMaxTotal(500);
        connectionManager.setDefaultMaxPerRoute(100);
        connectionManager.setValidateAfterInactivity(2000);

        httpClientBuilder = HttpClientBuilder.create();
        httpClientBuilder.setConnectionManager(connectionManager);

        // 设置重试策略(即便send请求了，只要异常依然重试)
        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true));

        // 设置默认的超时策略
        RequestConfig requestConfig =
            RequestConfig.custom()
                .setConnectionRequestTimeout(1000)
                .setConnectTimeout(5000)
                .setSocketTimeout(20000)
                .build();
        httpClientBuilder.setDefaultRequestConfig(requestConfig);
        return httpClientBuilder.build();
    }
}
